/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/24 23:23
  *
  * @file       pd_spi_hd.c
  * @brief      SPI peripheral hardware driver.
  *
  * @note       This file contains the hardware driver for the SPI peripheral.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/24     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_gpio.h>
#include "stm32f4xx_ll_spi.h"
#include <stdlib.h>
#include <stm32f4xx_ll_bus.h>
#include "pd_spi_hd.h"

#define SSEL(x, y)  SPIHardware_writeBit(x, y)

static void SPIHardware_writeBit(GPIO *gpio, FlagStatus BitVal) {
    if (gpio) gpio->GPIOx->BSRR = BitVal ? gpio->Pin : gpio->Pin << 16;
}


static void SPIHardware_init(SPI *super) {
    SPIHardware *this = (SPIHardware *) super;
    if (this->SPIx == SPI1)
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
    if (this->SPIx == SPI2)
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2);
    if (this->SPIx == SPI3)
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI3);

    for (int i = 0; i < super->sselSize; ++i) {
        super->ssel[i]->init(super->ssel[i]);
    }
    if (this->spi.sclk) this->spi.sclk->init(this->spi.sclk);
    if (this->spi.mosi) this->spi.mosi->init(this->spi.mosi);
    if (this->spi.miso) this->spi.miso->init(this->spi.miso);

    LL_SPI_InitTypeDef spiInitTypeDef = {0};
    LL_SPI_StructInit(&spiInitTypeDef);
    spiInitTypeDef.TransferDirection = this->TransferDirection;
    spiInitTypeDef.Mode = this->Mode;
    spiInitTypeDef.DataWidth = this->DataWidth;
    spiInitTypeDef.ClockPolarity = this->ClockPolarity;
    spiInitTypeDef.ClockPhase = this->ClockPhase;
    spiInitTypeDef.NSS = this->NSS;
    spiInitTypeDef.BaudRate = this->BaudRate;
    spiInitTypeDef.BitOrder = this->BitOrder;
    spiInitTypeDef.CRCCalculation = this->CRCCalculation;
    spiInitTypeDef.CRCPoly = this->CRCPoly;
    LL_SPI_Init(this->SPIx, &spiInitTypeDef);

    LL_SPI_Enable(this->SPIx);

    // Set all slave select
    for (int i = 0; i < super->sselSize; ++i) {
        SSEL(super->ssel[i], 1);
    }
}

static void SPIHardware_start(SPI *super, GPIO *ssel) {
    SSEL(ssel, 0);  // Slave select enable
}

static void SPIHardware_stop(SPI *super, GPIO *ssel) {
    SSEL(ssel, 1);  // Slave select disable
}

static uint8_t SPIHardware_swapByte(SPI *super, uint8_t dat) {
    SPIHardware *this = (SPIHardware *) super;

    while (!LL_SPI_IsActiveFlag_TXE(this->SPIx));   // Wait transmit empty
    LL_SPI_TransmitData8(this->SPIx, dat);  // Send 8 bits data

    while (LL_SPI_IsActiveFlag_BSY(this->SPIx));

    while (!LL_SPI_IsActiveFlag_RXNE(this->SPIx));  // Wait receive not empty
    uint8_t ret = LL_SPI_ReceiveData8(this->SPIx);  // receive 8 bits data

    return ret;
}

static void SPIHardware_preConfigGPIO(GPIO *gpio, SPI_TypeDef *SPIx, uint32_t NSS) {
    if (!gpio) return;

    if (NSS == LL_SPI_NSS_SOFT) {
        // Set general GPIO if the slave select is control by software
        gpio->Mode = LL_GPIO_MODE_OUTPUT;
        gpio->Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
        gpio->Pull = LL_GPIO_PULL_UP;
    } else {
        // Set alternate GPIO if the slave select is control by hardware
        gpio->Mode = LL_GPIO_MODE_ALTERNATE;
        gpio->Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
        gpio->Pull = LL_GPIO_PULL_NO;
        if (SPIx == SPI3)
            gpio->Alternate = LL_GPIO_AF_6;
        else if (SPIx == SPI1 || SPIx == SPI2)
            gpio->Alternate = LL_GPIO_AF_5;
        else {}
    }
}

static SPIHardware *build(SPI_TypeDef *SPIx, SPIGPIO *spiGPIO) {
    SPIHardware *spiHardware = malloc(sizeof(SPIHardware));

    Driver_add(spiHardware, GEN_PERM);

    // SPI GPIO pre-config
    GPIO *gpios[] = {spiGPIO->sclk, spiGPIO->mosi, spiGPIO->miso};
    for (int i = 0; i < 3; ++i) {
        SPIHardware_preConfigGPIO(gpios[i], SPIx, LL_SPI_NSS_HARD_OUTPUT);
    }
    for (int i = 0; i < spiGPIO->sselSize; ++i) {
        SPIHardware_preConfigGPIO(spiGPIO->ssel[i], SPIx, LL_SPI_NSS_SOFT);
    }

    spiHardware->SPIx              = SPIx;
    spiHardware->TransferDirection = LL_SPI_FULL_DUPLEX;
    spiHardware->Mode              = LL_SPI_MODE_MASTER;
    spiHardware->DataWidth         = LL_SPI_DATAWIDTH_8BIT;
    spiHardware->ClockPolarity     = LL_SPI_POLARITY_HIGH;
    spiHardware->ClockPhase        = LL_SPI_PHASE_2EDGE;
    spiHardware->NSS               = LL_SPI_NSS_SOFT;
    spiHardware->BaudRate          = LL_SPI_BAUDRATEPRESCALER_DIV2;
    spiHardware->BitOrder          = LL_SPI_MSB_FIRST;
    spiHardware->CRCCalculation    = LL_SPI_CRCCALCULATION_DISABLE;
    spiHardware->CRCPoly           = 10U;

    SPI *spi = SPIBuilder.build(spiGPIO->ssel, spiGPIO->sselSize, spiGPIO->sclk, spiGPIO->mosi, spiGPIO->miso);
    spiHardware->spi          = *spi;
    spiHardware->spi.init     = &SPIHardware_init;
    spiHardware->spi.start    = &SPIHardware_start;
    spiHardware->spi.swapByte = &SPIHardware_swapByte;
    spiHardware->spi.stop     = &SPIHardware_stop;

    return spiHardware;
}

const struct SPIHardwareClass SPIHardwareBuilder = {.build = &build};

